#include "kernel.h"
#include "globals.h"
#include "types.h"
#include "errno.h"

#include "util/string.h"
#include "util/printf.h"
#include "util/debug.h"

#include "fs/dirent.h"
#include "fs/fcntl.h"
#include "fs/stat.h"
#include "fs/vfs.h"
#include "fs/vnode.h"

#include "mm/kmalloc.h"

/* This takes a base 'dir', a 'name', its 'len', and a result vnode.
 * Most of the work should be done by the vnode's implementation
 * specific lookup() function, but you may want to special case
 * "." and/or ".." here depnding on your implementation.
 *
 * If dir has no lookup(), return -ENOTDIR.
 *
 * Note: returns with the vnode refcount on *result incremented.
 */
int
lookup(vnode_t *dir, const char *name, size_t len, vnode_t **result)
{
    KASSERT(NULL != dir);
    KASSERT(NULL != name);
    KASSERT(NULL != result);
/* Yu code below v1.0 */
    int retval;

    if( dir->vn_ops->lookup == NULL ){
    	    return -ENOTDIR;
    }

    retval = dir->vn_ops->lookup( dir, name, len, result );
   
    return retval;
/* Yu code above v1.0 */
}


/* When successful this function returns data in the following "out"-arguments:
 *  o res_vnode: the vnode of the parent directory of "name"
 *  o name: the `basename' (the element of the pathname)
 *  o namelen: the length of the basename
 *
 * For example: dir_namev("/s5fs/bin/ls", &namelen, &name, NULL,
 * &res_vnode) would put 2 in namelen, "ls" in name, and a pointer to the
 * vnode corresponding to "/s5fs/bin" in res_vnode.
 *
 * The "base" argument defines where we start resolving the path from:
 * A base value of NULL means to use the process's current working directory,
 * curproc->p_cwd.  If pathname[0] == '/', ignore base and start with
 * vfs_root_vn.  dir_namev() should call lookup() to take care of resolving each
 * piece of the pathname.
 *
 * Note: A successful call to this causes vnode refcount on *res_vnode to
 * be incremented.
 */
int
dir_namev(const char *pathname, size_t *namelen, const char **name,
          vnode_t *base, vnode_t **res_vnode)
{
     	KASSERT(NULL != pathname);
	KASSERT(NULL != namelen);
	KASSERT(NULL != name);
	KASSERT(NULL != res_vnode);

/* Yu code below v1.0.1 */
        vnode_t *pathbase;
	vnode_t *original_base;
        char *path_buf;
        char *elem_buf;
        char *path_piece_prt;
        char *next_path_piece_prt;
        size_t pathname_len;
        size_t path_piece_len;
        int while_loop_count = 0;
        size_t slash_count = 0;
        int retval;

        /* verify the pathname length */
        pathname_len = strlen(pathname);
        if( pathname_len > MAXPATHLEN) {
            return -ENAMETOOLONG;
        }

        /* determine the base vnode */
        if( pathname[0] == '/' ){
            pathbase = vfs_root_vn;
        } else {
            if(base == NULL){
                pathbase = curproc->p_cwd;
            } else {
                pathbase = base;
            }
        }

        /* analyze the pathname */
        path_buf = (char*) kmalloc( pathname_len + (size_t) sizeof(char) );
        KASSERT( path_buf != NULL);
        strcpy( path_buf,  pathname );
        if( path_buf[0] == '/' ) {
        	for(path_piece_prt = path_buf, slash_count = 0;
        	    *path_piece_prt == '/';
        	    ){
        	    path_piece_prt++;
        	    slash_count++;
        	}
        	if( pathname_len == slash_count){  /* handle "/" root below */
        	    vref( vfs_root_vn );
        	    *res_vnode = vfs_root_vn;
        	    elem_buf =(char*) kmalloc( sizeof(char) + sizeof(char) );
        	    strcpy ( elem_buf, "/" );
        	    *name = elem_buf;
        	    *namelen = 1;
            	kfree((void*)path_buf);
        	    return 0;
        	}
        } else {
        	   path_piece_prt = path_buf;
        }
        if ( path_buf[pathname_len-1] == '/' ) {
            /* pre-process the pathname which ends with '/' */ 
            for(next_path_piece_prt = path_buf + pathname_len-1;
                *next_path_piece_prt == '/';
                ) {
                *next_path_piece_prt = '\0';
                next_path_piece_prt--;
            }
        }
        next_path_piece_prt = strchr(path_piece_prt, '/');

        while ( next_path_piece_prt != NULL ){
            *next_path_piece_prt = '\0';
            next_path_piece_prt++;
            for(;
                *next_path_piece_prt =='/';
                ) {
                next_path_piece_prt++;
            }
            path_piece_len = strlen( path_piece_prt );
	    original_base = pathbase;
            retval = lookup( pathbase, path_piece_prt, path_piece_len, &pathbase );
            if( while_loop_count != 0 ) {
            	vput( original_base ); /* otherwise, refcount leaks */
            }
    	    dbgq(DBG_THR, "dir_namev():  look up return, curproc is %s\n", curproc->p_comm);
            if( retval != 0 ) {
                kfree(path_buf);
        	    return retval; /* -ENOTDIR or -ENOENT*/
            }
            path_piece_prt = next_path_piece_prt;
            next_path_piece_prt = strchr(path_piece_prt, '/');
            while_loop_count++;
        }
        path_piece_len = strlen(path_piece_prt);
        elem_buf =(char*) kmalloc( path_piece_len+sizeof(char));
        KASSERT( elem_buf != NULL );
        strcpy( elem_buf, path_piece_prt );
        kfree(path_buf);

    	dbgq(DBG_THR, "dir_namev():  dir_namev leave, curproc is %s\n", curproc->p_comm);
        if( while_loop_count == 0 ) {
            vref( pathbase );
        }
        *res_vnode = pathbase;
        *name = elem_buf;
        *namelen = path_piece_len;
        KASSERT(NULL != pathbase);
        return 0;
/* Yu code above v1.0.1 */
}

/* This returns in res_vnode the vnode requested by the other parameters.
 * It makes use of dir_namev and lookup to find the specified vnode (if it
 * exists).  flag is right out of the parameters to open(2); see
 * <weenix/fnctl.h>.  If the O_CREAT flag is specified, and the file does
 * not exist call create() in the parent directory vnode.
 *
 * Note: Increments vnode refcount on *res_vnode.
 */
int
open_namev(const char *pathname, int flag, vnode_t **res_vnode, vnode_t *base)
{
/* Yu code below v1.0.1 */
        vnode_t *parent_node;
        char *elem_name;
        size_t elem_name_len;
        int retval;

    	dbgq(DBG_THR, "open_namev():  call dir_namev, curproc is %s\n", curproc->p_comm);
        retval = dir_namev( pathname, &elem_name_len, (const char**) &elem_name, base, &parent_node);
    	dbgq(DBG_THR, "open_namev():  dir_namev return, curproc is %s\n", curproc->p_comm);
        if( retval != 0 ) {
            return retval;  /* -ENAMETOOLONG or -ENOTDIR or -ENOENT*/
        }
        if( elem_name_len == 1 && strcmp( elem_name, "/" ) == 0 ) {/*handle "/" root */
            *res_vnode = vfs_root_vn;
            kfree( elem_name ); /*kmalloc in dir_namev( )*/
            return 0;
        }
        retval = lookup( parent_node, elem_name, elem_name_len, res_vnode);
        if( ( retval == -ENOENT ) && ( flag& O_CREAT ) ) {
	    KASSERT(NULL != parent_node->vn_ops->create);
            retval = parent_node->vn_ops->create( parent_node, elem_name, elem_name_len,res_vnode );
        }
    	dbgq(DBG_THR, "open_namev():  leave\n, curproc is %s\n", curproc->p_comm);
        vput( parent_node );
        kfree( elem_name ); /*kmalloc in dir_namev( )*/
        return retval;
/* Yu code above v1.0.1 */
}

#ifdef __GETCWD__
/* Finds the name of 'entry' in the directory 'dir'. The name is writen
 * to the given buffer. On success 0 is returned. If 'dir' does not
 * contain 'entry' then -ENOENT is returned. If the given buffer cannot
 * hold the result then it is filled with as many characters as possible
 * and a null terminator, -ERANGE is returned.
 *
 * Files can be uniquely identified within a file system by their
 * inode numbers. */
int
lookup_name(vnode_t *dir, vnode_t *entry, char *buf, size_t size)
{
        NOT_YET_IMPLEMENTED("GETCWD: lookup_name");
        return -ENOENT;
}


/* Used to find the absolute path of the directory 'dir'. Since
 * directories cannot have more than one link there is always
 * a unique solution. The path is writen to the given buffer.
 * On success 0 is returned. On error this function returns a
 * negative error code. See the man page for getcwd(3) for
 * possible errors. Even if an error code is returned the buffer
 * will be filled with a valid string which has some partial
 * information about the wanted path. */
ssize_t
lookup_dirpath(vnode_t *dir, char *buf, size_t osize)
{
        NOT_YET_IMPLEMENTED("GETCWD: lookup_dirpath");

        return -ENOENT;
}
#endif /* __GETCWD__ */
